﻿using Ndns.Cache;
using Ndns.Forwarder;
using Ndns.ServerHost.Abstractions;
using Ndns.ServerHost.Builder;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Topshelf;
using Topshelf.Runtime;

namespace SimpleDnsForwarder
{
    class ForwarderApplication : ServiceControl
    {
        private CancellationTokenSource _stopCts;
        private ManualResetEventSlim _stopped;

        public ForwarderApplication(HostSettings settings)
        {
            
        }

        public bool Start(HostControl hostControl)
        {
            _stopCts = new CancellationTokenSource();
            _stopped = new ManualResetEventSlim(false);

            var host = BuildHost();
            var task = host.RunAsync(_stopCts.Token);

            task.ContinueWith((t, s) =>
            {
                ((ForwarderApplication)s)._stopped.Set();
            }, this, TaskContinuationOptions.ExecuteSynchronously);
            return true;
        }

        public bool Stop(HostControl hostControl)
        {
            _stopCts.Cancel();
            _stopped.Wait();

            return true;
        }

        private IDnsServerHost BuildHost()
        {
            var host = new DnsServerHostBuilder()
                .UseDefaultContextFactory()
                .ConfigureServices(services =>
                {
                    services.AddConsoleLogger();

                    services.AddMemoryCache(new DnsCacheOptions
                    {
                        EntryCountLimit = 2000
                    });
                })
                .ConfigureMiddlewares(app =>
                {
                    app.UseListener(DnsProtocol.Tcp, new IPEndPoint(IPAddress.Any, 53));
                    app.UseListener(DnsProtocol.Tcp, new IPEndPoint(IPAddress.IPv6Any, 53));
                    app.UseListener(DnsProtocol.Udp, new IPEndPoint(IPAddress.Any, 53));
                    app.UseListener(DnsProtocol.Udp, new IPEndPoint(IPAddress.IPv6Any, 53));

                    var addresses = Dns.GetHostAddresses(Dns.GetHostName());
                    foreach (var item in addresses)
                    {
                        app.UseListener(DnsProtocol.Tcp, new IPEndPoint(item, 53));
                    }

                    app.UseHostsFile(Path.Combine(Directory.GetCurrentDirectory(), "hosts.txt"), new Ndns.HostsFile.HostsFileMiddlewareOptions
                    {
                        EnableCNAMEResolution = true,
                        IsRecursionAvailable = true
                    });

                    app.UseMemoryCache();

                    app.UseForwarders(new List<DnsForwarderInfo>
                    {
                        new DnsForwarderInfo(DnsProtocol.Udp, new IPEndPoint(IPAddress.Parse("114.114.114.114"), 53)),
                        new DnsForwarderInfo(DnsProtocol.Tcp, new IPEndPoint(IPAddress.Parse("114.114.114.114"), 53))
                    });
                })
                .Build();
            return host;
        }
    }
}
